Skip to content

fix(clerk-js): validate window navigation protocols and honor allowedRedirectProtocols#8961

Open
jacekradko wants to merge 4 commits into
mainfrom
jacek/validate-window-navigation-protocols
Open

fix(clerk-js): validate window navigation protocols and honor allowedRedirectProtocols#8961
jacekradko wants to merge 4 commits into
mainfrom
jacek/validate-window-navigation-protocols

Conversation

@jacekradko

@jacekradko jacekradko commented Jun 23, 2026

Copy link
Copy Markdown
Member

windowNavigate in @clerk/shared assigned its argument straight to window.location.href on the assumption that callers had already validated it. This change routes every internal browser navigation in clerk-js and @clerk/ui through one chokepoint, clerk.__internal_windowNavigate, which checks the resolved URL against the configured allowedRedirectProtocols (the http/https/wails/chrome-extension defaults plus any customer additions) and rejects scheme-relative inputs like //host before parsing.

The part worth a close look is the version-skew path: @clerk/ui can run against an older clerk-js that does not expose __internal_windowNavigate, so clerkWindowNavigate falls back to the bare @clerk/shared helper and the static allowlist, still rejecting the bad cases. The bare windowNavigate export stays for that reason and is now @deprecated in favor of the wrapper.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed missing redirect URL protocol validation for browser navigations, including multi-session add-account, so redirects only use permitted protocols (“fail closed” for unsafe schemes).
    • Improved consistency of full-page navigation across sign-in/sign-up and external verification flows, including when mixed component versions are in use.
    • Updated UI flows to prefer the Clerk instance–aware navigation path when available.
  • New Features

    • Introduced an internal navigation chokepoint with centralized protocol allowlist handling.
  • Tests

    • Added/expanded coverage for allowed/blocked protocols, scheme-relative inputs, and fallback behavior when the internal navigation hook is unavailable.

@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 25, 2026 12:12pm
swingset Ready Ready Preview, Comment Jun 25, 2026 12:12pm

Request Review

@changeset-bot

changeset-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e2995d9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@clerk/clerk-js Patch
@clerk/react Patch
@clerk/shared Patch
@clerk/ui Patch
@clerk/chrome-extension Patch
@clerk/electron Patch
@clerk/expo Patch
@clerk/nextjs Patch
@clerk/react-router Patch
@clerk/tanstack-react-start Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/expo-passkeys Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/headless Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nuxt Patch
@clerk/testing Patch
@clerk/vue Patch
@clerk/swingset Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fe5306b-d0d7-4d26-8f45-ab0e00c42630

📥 Commits

Reviewing files that changed from the base of the PR and between 16b7d03 and 6b919f9.

📒 Files selected for processing (3)
  • packages/shared/src/internal/clerk-js/__tests__/windowNavigate.test.ts
  • packages/shared/src/internal/clerk-js/windowNavigate.ts
  • packages/ui/src/utils/__tests__/windowNavigate.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/ui/src/utils/tests/windowNavigate.test.ts
  • packages/shared/src/internal/clerk-js/tests/windowNavigate.test.ts
  • packages/shared/src/internal/clerk-js/windowNavigate.ts

📝 Walkthrough

Walkthrough

A new __internal_windowNavigate chokepoint is added to Clerk and the shared Clerk interface. Shared navigation validation is tightened, UI and ClerkJS call sites move to the Clerk-aware bridge, and bundle size limits are updated.

Changes

Window navigation protocol allowlist

Layer / File(s) Summary
Shared types and Clerk interface contract
packages/shared/src/internal/clerk-js/windowNavigate.ts, packages/shared/src/types/clerk.ts, .changeset/windownavigate-protocol-allowlist.md
WindowNavigateOptions adds allowedProtocols, Clerk gains __internal_windowNavigate(to, opts?), and the changeset records the patch release.
Hardened windowNavigate implementation and tests
packages/shared/src/internal/clerk-js/windowNavigate.ts, packages/shared/src/internal/clerk-js/__tests__/windowNavigate.test.ts
windowNavigate now rejects normalized scheme-relative inputs, applies an effective protocol allowlist, and warns before aborting disallowed navigation. Tests cover allowed schemes, blocked schemes, scheme-relative forms, extended allowlists, and fail-closed fallback cases.
Clerk.__internal_windowNavigate and core routing
packages/clerk-js/src/core/clerk.ts, packages/clerk-js/src/core/resources/SignIn.ts, packages/clerk-js/src/core/resources/SignUp.ts
Clerk adds __internal_windowNavigate, navigate() routes deep-link and fallback paths through it, and SignIn/SignUp switch redirect and SSO navigation to the Clerk instance method.
Clerk-aware UI bridge and React forwarding
packages/react/src/isomorphicClerk.ts, packages/ui/src/utils/windowNavigate.ts, packages/ui/src/utils/__tests__/windowNavigate.test.ts
clerkWindowNavigate(clerk, to, opts) delegates to clerk.__internal_windowNavigate when available and falls back to shared windowNavigate otherwise. IsomorphicClerk forwards the method, and tests cover delegation, fallback, and fail-closed behavior.
UI call sites migrated to clerkWindowNavigate
packages/ui/src/components/UserButton/useMultisessionActions.tsx, packages/ui/src/components/UserProfile/EnterpriseAccountsSection.tsx, packages/ui/src/contexts/components/SessionTasks.ts, packages/ui/src/contexts/components/SignIn.ts, packages/ui/src/contexts/components/SignUp.ts
The add-account, enterprise verification, Safari ITP, and decorated redirect flows now pass a Clerk instance into clerkWindowNavigate.
Bundle size configuration updates
packages/clerk-js/bundlewatch.config.json
Bundle size limits for clerk.legacy.browser.js and clerk.native.js are updated.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • clerk/javascript#8831: Also changes the redirect/auth flow in packages/clerk-js/src/core/resources/SignIn.ts and SignUp.ts, so it touches the same navigation paths.

Suggested reviewers

  • tmilewski
  • jeremy-clerk

Poem

🐇 I hopped through redirects with protocol care,
No sneaky scheme-relative traps in the air.
__internal_windowNavigate keeps the path tight and true,
And older bundles still fail closed, too.
A nibble of code, a safer glide—
The bunny approves with a protocol stride.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the navigation protocol validation fix and honoring allowedRedirectProtocols.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch jacek/validate-window-navigation-protocols

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jun 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8961

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8961

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8961

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8961

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@8961

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@8961

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@8961

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8961

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8961

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8961

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8961

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8961

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8961

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8961

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8961

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8961

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8961

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8961

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8961

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8961

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8961

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8961

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8961

commit: e2995d9

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-25T12:14:11.220Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 2
🔴 Breaking changes 0
🟡 Non-breaking changes 1
🟢 Additions 3

🤖 This report was reviewed by claude-sonnet-4-6.


@clerk/clerk-js

Current version: 6.21.1
Recommended bump: MINOR → 6.22.0

Subpath .

🟢 Additions (1)

Added: Clerk.__internal_windowNavigate
+ __internal_windowNavigate: (to: URL | string, opts?: {
+         useStaticAllowlistOnly?: boolean;
+     }) => void;

Added property Clerk.__internal_windowNavigate

Subpath ./no-rhc

🟢 Additions (1)

Added: Clerk.__internal_windowNavigate
+ __internal_windowNavigate: (to: URL | string, opts?: {
+         useStaticAllowlistOnly?: boolean;
+     }) => void;

Added property Clerk.__internal_windowNavigate


@clerk/shared

Current version: 4.21.0
Recommended bump: MINOR → 4.22.0

Subpath ./internal/clerk-js/windowNavigate

🟡 Non-breaking Changes (1)

Modified: windowNavigate
- declare function windowNavigate(to: URL | string): void;
+ declare function windowNavigate(to: URL | string, options?: WindowNavigateOptions): void;

Static analyzer: Modified function windowNavigate: Optional parameter options was added

🤖 AI review (confirmed) (100%): An optional parameter options was added to windowNavigate; existing call sites that omit the second argument remain valid and well-typed.

🟢 Additions (1)

Added: WindowNavigateOptions
+ type WindowNavigateOptions = {
+   allowedProtocols?: ReadonlyArray<string>;
+ };

Added type alias WindowNavigateOptions


Report generated by Break Check

Last ran on e2995d9.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react/src/isomorphicClerk.ts`:
- Around line 268-270: The `__internal_windowNavigate` method in IsomorphicClerk
always exists as a forwarder, but when the clerkjs version lacks this method,
the optional chain silently fails and navigation is dropped without fallback.
Modify the `__internal_windowNavigate` implementation to check if clerkjs
actually supports this method before delegating to it, and provide an
appropriate fallback behavior (such as returning false or implementing
alternative navigation logic) when the method is not available on the underlying
clerkjs instance, ensuring that older clerkjs versions can still handle
navigation through their default mechanisms.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: e7aa3b33-c676-4786-b718-c746653758d4

📥 Commits

Reviewing files that changed from the base of the PR and between 52d310c and 0dbc430.

📒 Files selected for processing (15)
  • .changeset/windownavigate-protocol-allowlist.md
  • packages/clerk-js/src/core/clerk.ts
  • packages/clerk-js/src/core/resources/SignIn.ts
  • packages/clerk-js/src/core/resources/SignUp.ts
  • packages/react/src/isomorphicClerk.ts
  • packages/shared/src/internal/clerk-js/__tests__/windowNavigate.test.ts
  • packages/shared/src/internal/clerk-js/windowNavigate.ts
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/ui/src/components/UserProfile/EnterpriseAccountsSection.tsx
  • packages/ui/src/contexts/components/SessionTasks.ts
  • packages/ui/src/contexts/components/SignIn.ts
  • packages/ui/src/contexts/components/SignUp.ts
  • packages/ui/src/utils/__tests__/windowNavigate.test.ts
  • packages/ui/src/utils/windowNavigate.ts

Comment thread packages/react/src/isomorphicClerk.ts
*/
export function windowNavigate(to: URL | string): void {
export function windowNavigate(to: URL | string, options?: WindowNavigateOptions): void {
if (typeof to === 'string' && SCHEME_RELATIVE_PREFIX.test(to.trim())) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scheme-relative open-redirect bypass via control characters

trim() only strips leading/trailing whitespace, but new URL() (next line) strips tab/LF/CR from anywhere and leading C0 controls — so an interior or leading control char defeats this regex while still resolving to a scheme-relative URL. It then inherits the base https: scheme (allowlisted), so navigation proceeds cross-origin.

Bypasses against base https://victim-app.com/:

"/\t/evil.attacker.com"   // navigates cross-origin
"\x00//evil.attacker.com" // navigates cross-origin
"//evil.attacker.com"     // correctly blocked (only case covered)

The added tests only cover leading/trailing whitespace, which trim() handles, so this slips through.

Fix — strip parser-ignored chars before the test:

if (typeof to === 'string' && SCHEME_RELATIVE_PREFIX.test(to.replace(/[\u0000-\u001F]/g, ''))) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 6b919f9

The scheme-relative guard in windowNavigate used to.trim(), which only strips
leading/trailing whitespace. The URL parser additionally removes ASCII tab/LF/CR
from anywhere and strips leading C0 controls, so inputs like /\t/evil.com or
\x00//evil.com defeated the regex yet still resolved scheme-relative against the
allowlisted base scheme and redirected cross-origin. Normalize the same way the
parser does before testing for a scheme-relative prefix.
…-navigation-protocols

# Conflicts:
#	packages/clerk-js/bundlewatch.config.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants